from twisted.spread import pb
from twisted.internet import defer, reactor, protocol
from twisted.python import failure, components

from inner.space import auth, errors, actions, editors

import UserDict

class InnerSpaceBroker(pb.Broker):
	def __init__(self, isClient=1, security=pb.globalSecurity):
		pb.Broker.__init__(self, isClient, security)
		self.entity_tracker = {}
		self.waitingForAnswers = BrokerRequestHook(self.waitingForAnswers, self.entity_tracker)
		
	def proto_answer(self, requestID, netResult):
		"""(internal) Got an answer to a previously sent message.
		
		Look up the appropriate callback and call it.
		"""
		auth.user_activate(self.entity_tracker[requestID])
		
		d = self.waitingForAnswers[requestID]
		if(self.entity_tracker.has_key(requestID)):
			del self.entity_tracker[requestID]
		del self.waitingForAnswers[requestID]
		d.callback(self.unserialize(netResult))
		
		auth.user_deactivate()
		
	def proto_error(self, requestID, fail):
		"""(internal) Deal with an error.
		"""
		auth.user_activate(self.entity_tracker[requestID])
		
		d = self.waitingForAnswers[requestID]
		if(self.entity_tracker.has_key(requestID)):
			del self.entity_tracker[requestID]
		del self.waitingForAnswers[requestID]
		d.errback(self.unserialize(fail))
		
		auth.user_deactivate()

class BrokerRequestHook(UserDict.UserDict):
	def __init__(self, data, entity_tracker):
		UserDict.UserDict.__init__(self, data)
		self.entity_tracker = entity_tracker
		
	def __setitem__(self, key, value):
		self.data[key] = value
		self.entity_tracker[key] = auth.get_current_user()
	
	def __delitem__(self, key):
		del self.data[key]
		if(self.entity_tracker.has_key(key)):
			del self.entity_tracker[key]

class UserPerspective(pb.Avatar):
	def __init__(self, client, service, user):
		"""
		Create a new avatar that is connected to the given client/service.
		"""
		assert client is not None, "client is None"
		assert user is not None, "user is None"
		self.client = client
		self.service = service
		self.user = user
		
		# keeping this here seems to cause some kind of issues when connecting
		# for now, we'll have the client call "look here" after a connect
		#observations = self.user.get_location().get_observations(service.registry.get(0), self.get_client_code())
		#self.client.callRemote("observe", observations)
		
	def get_client_code(self):
		"""
		Return the code for this kind of client.
		"""
		return "PB1.0"
	
	def perspective_noop(self):
		return True
	
	def perspective_req_entity_editor(self, id, request_id):
		obj = self.service.registry.get(id)
		editors.edit_entity(self.user, obj, request_id)
	
	def perspective_req_code_editor(self, id, verb, request_id):
		obj = self.service.registry.get(id)
		editors.edit_verb(self.user, obj, verb, request_id)
		
	def perspective_req_property_editor(self, id, prop, request_id):
		obj = self.service.registry.get(id)
		editors.edit_property(self.user, obj, prop, request_id)
	
	def perspective_req_acl_editor(self, id, item, request_id):
		obj = self.service.registry.get(id)
		if(item):
			obj = obj._vdict[item]
		editors.edit_acl(self.user, obj, request_id)
	
	def perspective_get_entity_attributes(self, id):
		"""
		Called remotely by the client to get info for an object.
		"""
		obj = self.service.registry.get(id)
		return obj.get_details()

	def perspective_remove_verb(self, id, verb):
		obj = self.service.registry.get(id)
		obj.remove_verb(verb)
		
	def perspective_remove_property(self, id, prop):
		obj = self.service.registry.get(id)
		obj.remove_property(prop)
		
	def perspective_parse(self, command):
		"""
		Called remotely by the client to parse a string representing a command.
		"""
		actions.parse(self.user, self.service.registry, command);
		
	def perspectiveMessageReceived(self, broker, message, args, kw):
		#there's a little hack here that maybe isn't such a great idea,
		#but it works pretty well. We use auth.user_activate()
		#here, which means any time the user is doing something (either via
		#a verb or a deferred), this is set to the user object.
		auth.user_activate(self.user)
		
		#we don't really do a lot in these callback functions, because most
		#of the time, nothing happens when we write to the client.
		def ok(result):
			pass
		def ko(failure):
			print failure
		
		result = None
		
		# this nested try statement will allow us to catch the exceptions
		# we can reasonably expect to happen, while ensuring that other
		# exceptions (which need to be handled by the PB framework) will
		# not leave the current_user variable in an inappropriate state
		try:
			try:
				result = pb.Avatar.perspectiveMessageReceived(self, broker, message, args, kw)
			except errors.UserError, e:
				if(self.user.is_programmer()):
					result = repr(e)
				else:
					result = str(e)
				print "User caused exception: " + repr(e)
				self.client.callRemote('write', result).addCallback(ok).addErrback(ko)
			except errors.EngineError, e:
				print self.user, "raised", str(e)
				self.client.callRemote('write', str(e)).addCallback(ok).addErrback(ko)
		finally:
			auth.user_deactivate()
		
		return result
		
	def logout(self):
		#before we log the client out, we dereference (hopefully) the client object.
		reactor.callLater(1, actions.logout_code, self.user)
